<?php
ArrowController::importModel("org.arrowplatform.orm.ArrowOrmException");

/**
 * @author Pawel Giemza
 * @version 1.2
 * @package Arrow
 * @subpackage Orm
 * @link http://arrowplatform.org/
 * @copyright 2009 3code 
 * @license GNU LGPL
 * 
 * @date 2009-06-01 
*/

/**
 * Stores configuration data used by other ORM objects. 
 * 
 * This class is responsible for managing DB configuration data.
 * Other classes must use API defined here in order to obtain information
 * about available databases and their tables.
 * 
 * ArrowOrmConfig storef configuration options used by other classes of Arrow ORM library.
 */
class ArrowOrmConfig implements IArrowCacheable{
	
	/**
	 * Stores configuration data during execution of script so it does not 
	 * have to be read from file every time.
	 *
	 * @var Array
	 */
	private static $conf = array();	
	private static $options = array();
	
	/**
	 * Class configuration getter
	 * Returns configuration data (field names/types, primary/foreign keys, etc. ) of object specified in parameter.
	 *  
	 * @note If class is not specified configuration of all data objects is returned. 
	 *  
	 * @param String $class - name of the object 
	 * @return Array - object configuration data
	 */
	public static function getClassConf( $class = false ){
		//ArrowLogger::log("[ArrowOrmConfig] getClassConf()", ArrowLogger::EL_INFO );
		
		//load configuration data (only if required)
		if (empty(self::$conf))
			self::load();

		if($class == false)
			return self::$conf['object'];
		//return object configuration data or throw exception (if there is no configuration for given object)	
		if( !isset(self::$conf['object'][ $class ]) )
			throw new ArrowOrmException("No configuration data has been found for class '$class'."  );
		return self::$conf['object'][$class];
	}	
	
	/**
	 * Return configuration data (host, username, password, etc. ) of DB specified as a parameter
	 * if DB is not specified configuration of first DB will be returned. This function is used by SqlRouter class 
	 * 
	 * Example of use:
	 * 
	 * <code>
	 * 	$conf = Config::getDBConf("db1");	 
	 * </code>	 
	 * @param String $dbName - id of the database 
	 * @return Array - DB configuration data
	 */
	public static function getDBConf($dbName){
		//load configuration data (only if required)
		if (empty(self::$conf))
			self::load();
		
		//ArrowLogger::log("[ArrowOrmConfig] getDBConf()", ArrowLogger::EL_INFO );
		//w chili obecnej wybór po subdomennie nie jest obsługiwany 
		//baza wybiera się po podfolderze
		/*$urlParts = explode('.', $_SERVER['HTTP_HOST']);
		if($urlParts[0] == "www") unset($urlParts[0]);
		if(count($urlParts) > 2){
			//tutaj jest wybór po subdomene
			$sub = reset($urlParts);
			if(isset(self::$conf['database'][$sub]))
				return self::$conf['database'][$sub][$dbName];
		}*/
			
		//pobierz ostatni folder za scieżki
		$folder = explode('/',$_SERVER['REQUEST_URI']);
		if (count($folder)>=2){
			//we have subfolder
			$folder = $folder[count($folder)-2];
		}
		if(isset(self::$conf['database'][$folder]))
			return self::$conf['database'][$folder][$dbName];
		
		//domyślna baza danych
		$firstGroup = reset(self::$conf['database']);
		
		//return DB configuration data or throw exception (if there is no configuration for given DB)			
		if ($dbName==''){ //return default database
			return reset($firstGroup);
		}
			
		if( !isset($firstGroup[$dbName]) )
			throw new ArrowOrmException(array( "msg" => "No DB configuration data has been found for '$dbName'."  ));
		return $firstGroup[$dbName];
	}
	
	public static function getDefaultDbName(){
		if (empty(self::$conf))
			self::load();
		$firstGroup = reset(self::$conf['database']);
		$data = array_keys($firstGroup) ;
			
		return reset( $data );
	}
	
	public static function getModelPath($class){
		$conf = self::getClassConf($class);
		return $conf['attr']['classpath'];
	} 

	public static function getKeyName($class){
		$conf = self::getClassConf($class);
		return $conf['key']['primary'];
	} 
	
	public static function getFieldList($class){
		$conf = self::getClassConf($class);
		$fields = array();
		foreach ($conf['field'] as $val)
			$fields[$val['name']] = $val['name'];
		return $fields;
	} 

	public static function getRequiredFields($class){
		$conf = self::getClassConf($class);
		$fields = array();
		foreach ($conf['field'] as $val)
			if(isset($val['required']) && $val['required'] === 'true')
				$fields[$val['name']] = $val['name'];
		return $fields;
	} 
	
	/**
	 * Load configuration data into memory so it could be accessed easilly. 
	 * This function uses cache files generated by ArrowCacheProvider class. 
	 */
	private static function load() {
		//ArrowLogger::log("[ArrowOrmConfig] load()", ArrowLogger::EL_INFO );
		
		
    	//path to XML file storing the DB configuration data
		$mapping_xml =  ArrowController::$activeProject->getPath()."/conf/orm-conf.xml";
		
		//load DB configuration file cache to obtain list of databases           
		$config = new ArrowOrmConfig();
		$dbconf = ArrowCacheProvider::getFileCache($config, $mapping_xml, array( "mappingXML" => array( $mapping_xml ), "file_prefix" => ArrowController::$activeProject->getId()."_orm_config") );
	    if (empty($dbconf))
	     	throw new ArrowOrmException("Could not load DB configuration file", 1);
	    require_once $dbconf;
			$dbconf = $cache;
		//generate list of DB configuration files to load	
	    $filelist = array();
	    $filelist[] = $mapping_xml;
	    
	    $first = reset($dbconf['database']);
	    
	    foreach ($first as $key => $val){
            $filelist[] = dirname($mapping_xml).'/db-schema-'.$key.'.xml';    
	    }
	    //load cache
	    $cache_name = ArrowCacheProvider::getFileCache($config, $filelist, array( "mappingXML" => $filelist,   "file_prefix" => ArrowController::$activeProject->getId()."_orm_schema"));
	    require_once $cache_name;   
	    self::$conf = $cache;	  	
			//ArrowLogger::log("[ArrowOrmConfig] ORM configuration file loaded",ArrowLogger::EL_INFO);			
	}

	/**
	 * Part of IArrowCacheable interface. 
	 * This function parses and caches XML configuration files used by Arrow Orm library.
	 * @param Array $file_list - Names of files to be cached 
	 * @return Array - Parsed contents of configuration files
	 */
	public function generateCache($file_list){
		//ArrowLogger::log("[ArrowOrmConfig] generateCache()", ArrowLogger::EL_INFO );
		$foreignkeys = array();
    	$classes = array();
		$conf = array();

        
		foreach($file_list["mappingXML"] as $file){
			$foreignkeys = array();
			//load XML file
			$xml = file_get_contents($file);
			//namespace must be deleted in order for this importer to work
			$xml = str_replace('xmlns="http://db.apache.org/ddlutils/schema/1.1"','',$xml);
			$sxml = simplexml_load_string($xml);
	         	      
	      	//check whether DB configuration was loaded
            if(strpos($file, "orm-conf.xml") !== false ){			
            	foreach($sxml->databases->group as $group){
            		$attrs = $group->attributes();
            		if ($attrs['active']=='true'){
	            		$conf['database'][$group["name"].""] = array();
	            		foreach ($group->db as $obj){
	            			$conf['database'][$group["name"].""][$obj["id"].""]['group_name']=$group["name"]."";
	    	          	    foreach ($obj->children() as $tag =>$value){
	        	          		$tag = (String)$tag;
	        	          		$value = (String)$value;
	        	          		$conf['database'][$group["name"].""][$obj["id"].""][(String)$tag]=$value;			
	        	        	}      
	    	        	}
            		}
            	}

        	}else{

    	      	$tables = $sxml->xpath('/database/table');
    	      	//check whether configuration was loaded	    
    	      	
	        	foreach( $tables as $offset => $table ){		
					list($base_path, $class_path) = explode('|',$table['description']);
	          		$baseclass = explode('.',$base_path);
	          		$baseclass = end($baseclass);
	          		$class = explode('.',$class_path);
	          		$class = end($class);
	          		//$conf['object'][$class]['attr']['basename'] = $baseclass;
	          		//$conf['object'][$class]['attr']['basepath'] = $base_path;
	          		$conf['object'][$class]['attr']['classpath'] = $class_path;
	          		$conf['object'][$class]['attr']['class'] = $class;
	                       
	          		$conf['object'][$class]['attr']['table'] = (String)$table['name'];   
	          		$conf['object'][$class]['attr']['db'] = (String)$sxml['name'];   
	          		$classes[(String)$table['name']] = $class;
	                 
	          		foreach ($table->attributes() as $tag =>$value){
	          			if($tag != "description")
	           				$conf['object'][(String)$class]['attr'][(String)$tag]=(String)$value;	
	          		}			 
	           
	          		foreach( $table->column as $column){
	            		
	          			$new_field = array();
	          			
	            
	            		foreach ($column->attributes() as $tag =>$value){
	            			if( $tag == "primaryKey" && $value=='true' )
	                			$conf['object'][$class]['key']['primary']=(String)$column['name'];
	            			/*if( $tag == 'size' || $tag == 'type'  || $tag == 'primaryKey' || $tag == 'javaName' || $tag == 'default' || $tag == 'autoIncrement' )
	            				continue;
	            			*/
	            			if ($tag == 'name'  || $tag == 'default')	
	              				$new_field[(String)$tag]=(String)$value;
	              			if ( ( ($tag == 'required' || $tag == 'autoIncrement') && $value=='true') )
		              			$new_field[(String)$tag]=(String)$value;
		              		if ( $tag == 'type' )
		              			$new_field[(String)$tag]=(String)$value;
	              			
	            		}
	            		$conf['object'][$class]['field'][(String)$column['name']]= $new_field;
	          		}
	          		foreach( $table->{'foreign-key'} as $fkey){
	            		foreach( $fkey->reference as $ref){
	               			$foreignkeys[] = array('class'=>$class, 'fTable'=>(String)$fkey['foreignTable'], 'local'=> (String)$ref['local'], 'foreign'=> (String)$ref['foreign'] ); 
	            		}
	          		}        
	        	}
	        	//foreign key translation     
	        	foreach( $foreignkeys as $fkey)
	        	{          
	          		$otherClass = $classes[$fkey['fTable']];
	          		if (isset($conf['object'][ $fkey['class'] ]['key']['foreign'][$otherClass])){
	          			$add = $conf['object'][ $fkey['class'] ]['key']['foreign'][$otherClass];
	          			if (is_array($add))
	          				$conf['object'][ $fkey['class'] ]['key']['foreign'][$otherClass][] = $fkey['local']; 
	          			else
	          				$conf['object'][ $fkey['class'] ]['key']['foreign'][$otherClass] = array($add,$fkey['local']);
	          		}
	          		else
	          			$conf['object'][ $fkey['class'] ]['key']['foreign'][$otherClass]=$fkey['local'];      
	        	}
    	      	
    	    }
	    }	
    	return $conf;
  	}
  	
  	public static function addJoinConf($class, $targetCol, $targetClass, $col='id' ){
  		 //those names are incorrect in order to make system work
  		//real values are :  class, column, targetClass, targetColumn
  		
  		//ArrowLogger::log("[ArrowOrmConfig] getClassConf()", ArrowLogger::EL_INFO );
		//load configuration data (only if required)
		if (empty(self::$conf))
			self::load();	
			
		//return object configuration data or throw exception (if there is no configuration for given object)	
		if( !isset(self::$conf['object'][ $class ]) )
			throw new ArrowOrmException("No configuration data has been found for class '$class'."  );
	
          if (isset(self::$conf['object'][$class]['key']['foreign'][$targetClass])){
          	$add = self::$conf['object'][$class]['key']['foreign'][$targetClass];
          	if (is_array($add))
          		self::$conf['object'][$class ]['key']['foreign'][$targetClass][] = $targetCol; 
          	else
          		self::$conf['object'][$class ]['key']['foreign'][$targetClass] = array($add,$targetCol);
          }
          else
          	self::$conf['object'][$class ]['key']['foreign'][$targetClass]=$targetCol;
  	}
  	
  	public static function removeJoinConf($class ,$targetCol, $targetClass, $col='' ){
  		//those names are incorrect in order to make system work
  		//real values are :  $class, $column, $targetClass, $targetColumn
  		
		if (empty(self::$conf))
			self::load();	
			
		//return object configuration data or throw exception (if there is no configuration for given object)	
		if( !isset(self::$conf['object'][ $class ]) )
			throw new ArrowOrmException("No configuration data has been found for class '$class'."  );
	
		$data =null;
		if (isset(self::$conf['object'][$class]['key']['foreign'][$targetClass]))
			$data = self::$conf['object'][$class]['key']['foreign'][$targetClass];
		else
			throw new ArrowException('No join to remove');

		if (is_array($data)){
			foreach ($data as $key => $val){
				if ($val == $targetCol){
					unset(self::$conf['object'][$class]['key']['foreign'][$targetClass][$key]);
					return; 
				}
				throw new ArrowException('No join to remove');
			}	
		}
		elseif ($data == $target){
			unset(self::$conf['object'][$class]['key']['foreign'][$targetClass]);
		}
		else
			throw new ArrowException('No join to remove');
  	}
  	
  	public static function getOption($name){
  		if (isset(self::$options[$name]))
		  	return self::$options[$name];
		return null;
  	}
  	
  	public static function setOption($name,$value){
		self::$options[$name] = $value;
  	}
}
?>